Function Hooking
Table of content
Definition
A hook is a callback added to a method that will redirect program execution flow. For example, EDR can add hooks to sensitive functions to check the different parameters used.
The hook can be implemented easily with a JMP added at the beginning of the function or through specific callback functions such as NtSetProcessInformation.
Hook example
x86 : Add JMP patch
The following x86 code hooks the MessageBoxA function and redirect the execution flow to another function:
#include <windows.h>
#include <stdio.h>
#define BYTES_REQUIRED 6
// Function where the execution flow will be redirected
int __stdcall HookedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType){
printf("\n[ HOOKED MESSAGEBOXA ]\n");
printf("Arguments:\n");
printf(" 1. lpText: %s\n", lpText);
printf(" 2. lpCaption: %s\n", lpCaption);
printf(" 3. uType: %ld\n", uType);
return 1;
}
int main()
{
SIZE_T lpNumberOfBytesRead = 0;
HMODULE hModule = NULL;
FARPROC pMessageBoxAFunc = NULL;
char pMessageBoxABytes[BYTES_REQUIRED];
void* pHookedMessageBoxFunc = &HookedMessageBoxA;
hModule = LoadLibraryA("user32.dll");
if (!hModule){
return -1;
}
pMessageBoxAFunc = GetProcAddress(hModule, "MessageBoxA");
if (ReadProcessMemory(GetCurrentProcess(), pMessageBoxAFunc, pMessageBoxABytes, BYTES_REQUIRED, &lpNumberOfBytesRead) == FALSE){
printf("[!] ReadProcessMemory: %ld\n", GetLastError());
return -1;
}
// Add the hook :
// JMP HookedMessageBoxFunc
// RET
char patch[BYTES_REQUIRED] = { 0 };
// JMP OPCODE
memcpy_s(patch, 1, "\x68", 1);
// Function address where the execution flow will be redirect
memcpy_s(patch + 1, 4, &pHookedMessageBoxFunc, 4);
// RET OPCODE
memcpy_s(patch + 5, 1, "\xC3", 1);
// Write the patch
if (WriteProcessMemory(GetCurrentProcess(), (LPVOID)pMessageBoxAFunc, patch, sizeof(patch), &lpNumberOfBytesRead) == FALSE){
printf("[!] WriteProcessMemory: %ld\n", GetLastError());
return -1;
}
// Call the hooked function
MessageBoxA(NULL, "AAAAA", "BBBBB", MB_OK);
return 0;
}
/*
Result :
[ HOOKED MESSAGEBOXA ]
-> Arguments:
1. lpText: AAAAA
2. lpCaption: BBBBB
3. uType: 0
*/
For x64 the patch's OPCODE must be changed
x86 : NtSetProcessInformation - Hook syscall results
The NtSetProcessInformation is called every time the kernel sent back a value to the usermode. When a syscall is performed, the kernel send back the result to the usermode function and call the NtSetProcessInformation function first.
The NtSetProcessInformation takes as an argument a function pointer used as a callback function. Thus, this function will be executed each time the kernel communicates with the usermode. It then can be used to intercept any system calls performed, any process or thread created etc...
Before calling the callback, the registers must be saved in the stack. Then the callback function is called and the registers are popped out from the stack. this this for more explanations.
R10 contains the last instruction pointer. When the callback is finished, just jump to R10 to continue the normal execution flow.
This is summarized in the following ASM code :
include ksamd64.inc
; the callback function that will be called and defined in the C file
EXTERN hook:NEAR
.code
medium PROC
; https://docs.microsoft.com/en-us/cpp/build/caller-callee-saved-registers
push rax ; return value if system call
push rcx
push RBX
push RBP
push RDI
push RSI
push RSP
push R12
push R13
push R14
push R15
; allocate stack
sub rsp, 1000h
; set parameters for the hook function
mov rdx, rax
mov rcx, r10
; call the hook function
call hook
; erase the allocated stack
add rsp, 1000h
; repop registers
pop R15
pop R14
pop R13
pop R12
pop RSP
pop RSI
pop RDI
pop RBP
pop RBX
pop rcx
pop rax
; jump to the normal execution flow
jmp R10
medium ENDP
END
Resource
- Nirvana Hooking : https://www.youtube.com/watch?v=pHyWyH804xE